// config.cpp
// Copyright (C) 2008 Sam Bateman
// samuel.bateman@gmail.com
//
// This file is part of DVD On Fire.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

//! @file config.cpp
//! @author Sam Bateman
//! @brief Source file for the Config class.

#include <cctype>
#include <cstdio>
#include <cstdlib>
#include <expat.h>
#include <map>
#include <string>
#include "config.h"
using namespace std;

namespace dvdonfire
{
    namespace lib
    {
        // class ConfigParser {{{
        class ConfigParser
        {
        public:
            // ConfigParser(o) {{{
            ConfigParser(map<string, string>& o)
            : options(o), depth(0) {}
            // }}}
            // data(raw, len) {{{
            void data(const char *raw, int len)
            {
                // ignore things that are only whitespace
                string str(raw, len);
                bool bogus = true;
                for (size_t i = 0; i < str.size(); ++i){
                    if (!isspace(str[i])){
                        bogus = false;
                        break;
                    }
                }
                if (bogus) return;

                // this must be good
                options[current] = str;
            }
            // }}}
            // end(element) {{{
            void end(const string& element)
            {
                --depth;
                if (depth == 0) current = "";
                else if (depth > 0){
                    current.erase(current.size() - element.size() - 1);
                }
            }
            // }}}
            // start(element) {{{
            void start(const string& element)
            {
                if (depth == 0) current = element;
                else if (depth > 0) current += '.' + element;
                ++depth;
            }
            // }}}

        private:
            // implementation
            map<string, string>& options;
            string current;
            int depth;
        };
        // }}}
        // configElementData(user, data, len) {{{
        void configElementData(void *user, const char *data, int len)
        {
            ConfigParser *parser = (ConfigParser *)user;
            parser->data(data, len);
        }
        // }}}
        // configElementEnd(user, element) {{{
        void configElementEnd(void *user, const char *element)
        {
            ConfigParser *parser = (ConfigParser *)user;
            parser->end(element);
        }
        // }}}
        // configElementStart(user, element) {{{
        void configElementStart(void *user, const char *element, const char **)
        {
            ConfigParser *parser = (ConfigParser *)user;
            parser->start(element);
        }
        // }}}

        // struct Config::Impl {{{
        struct Config::Impl
        {
            map<string, string> options;
        };
        // }}}
        // Config::Config() {{{
        //! @brief Constructor.
        Config::Config()
        {
            // open the configuration file
            string file = (string)getenv("HOME") + "/.dvdonfire/conf.xml";
            FILE *fp = fopen(file.c_str(), "r");
            if (!fp){
                fprintf(stderr, "Couldn't open config file \"%s\"\n",
                        file.c_str());
                exit(EXIT_FAILURE);
            }

            // parse the xml data
            impl = new Impl;
            ConfigParser *parser = new ConfigParser(impl->options);
            XML_Parser p = XML_ParserCreate(NULL);
            XML_SetUserData(p, parser);
            XML_SetElementHandler(p, configElementStart, configElementEnd);
            XML_SetCharacterDataHandler(p, configElementData);
            const int size = 1024;
            char buf[size];
            bool done = false;
            while (!done){
                int r = fread(buf, 1, size, fp);
                done = feof(fp);
                if (!XML_Parse(p, buf, r, done)){
                    fprintf(stderr, "Config file parse error, line %d:\n%s\n",
                            (int)XML_GetCurrentLineNumber(p),
                            XML_ErrorString(XML_GetErrorCode(p)));
                    exit(EXIT_FAILURE);
                }
            }
            XML_ParserFree(p);
            delete parser;
        }
        // }}}
        // Config::~Config() {{{
        //! @brief Destructor.
        Config::~Config()
        {
            delete impl;
        }
        // }}}
        // Config::get(key) {{{
        //! @brief Retrieves a configuration option.
        //! @param key Option to retrieve.
        //! @return The value for the configuration option.
        string Config::get(const std::string& key) const
        {
            return impl->options["dvdonfire." + key];
        }
        // }}}
        // Config::set(key, value) {{{
        //! @brief Sets a configuration option.
        //! @param key Option to set.
        //! @param value Value to set the option to.
        void Config::set(const std::string& key, const std::string& value)
        {
            impl->options[key] = value;
        }
        // }}}
    }
}
